﻿using System;
using System.Drawing;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

using COUP.Core.Entities;

namespace COUP.Core
{
    public class Sector
    {
        private Map _mapParent = null;
        private int _objectCount, _depth;
        private List<iEntity> _objects = null;
        private Sector _parent = null;
        private Sector _childTL = null;
        private Sector _childTR = null;
        private Sector _childBL = null;
        private Sector _childBR = null;

        private List<iEntity> Objects
        {
            get
            {
                if (_objects != null)
                    _objectCount = _objects.Count;
                return _objects;
            }
            set
            {
                _objects = value;
            }            
        }
        public Rectangle Bounds { get; set;}

        public Map MapParent
        {
            get { return _mapParent; }
        }

        public Sector(Point Start, Size _Size, Map Parent)
        {
            _mapParent = Parent;
            Bounds = new Rectangle(Start, _Size);
        }

        public Sector(Rectangle Area, Sector Parent, Map MapParent)
        {
            _mapParent = MapParent;
            _parent = Parent;
            Bounds = Area;
            _depth = Parent._depth + 1;
        }

        public void Insert(iEntity Entity, bool ForcedInsert = false)
        {
            if (!Bounds.IntersectsWith(Entity.Area))
            {
                _parent.Insert(Entity);
                return;
            }

            if (Objects == null || (_objectCount + 1 < _mapParent.MAX_OBJECTS && _childBL == null))
                Add(Entity);
            else
            {
                if (_childBL == null && _depth < _mapParent.MAX_DEPTH)
                    Divide();

                if (_objectCount + 1 >= _mapParent.MAX_OBJECTS)
                    MoveExisting();

                Sector Dest = GetDesinationTree(Entity);

                if (Dest == this)
                    Add(Entity);
                else
                    Dest.Insert(Entity);
            }
        }

        public void Remove(iEntity Entity)
        {
            if (Objects == null)
                return;

            if (Objects.Contains(Entity))
                Objects.Remove(Entity);

            if (Count == 0 && _objectCount == 0)
            {
                Objects = null;
                if (_childBL != null)
                    if (GetChildCount == 0)
                        RemoveDivisions();
            }
        }

        public void Move(iEntity Entity)
        {
            this.Remove(Entity);

            if (!Bounds.IntersectsWith(Entity.Area))
            {
                _parent.Insert(Entity);
                return;
            }

            Sector Dest = GetDesinationTree(Entity);

            if (Dest == this)
                Add(Entity);
            else
                Dest.Insert(Entity);
        }

        public void Add(iEntity Entity)
        {
            if (Objects == null)
                Objects = new List<iEntity>();

            Objects.Add(Entity);
            Entity.CurrentSector = this;
        }

        public iEntity GetEntity(int UniqueID)
        {
            if (Objects == null)
                return null;

            iEntity Target = Objects.Find((iEntity Entity) => { return Entity.UniqueID == UniqueID;});

            if (Target.UniqueID == 0)
                return null;

            return Target;
        }

        public iEntity GetEntity(string Name)
        {
            if (Objects == null)
                return null;

            iEntity Target = Objects.Find((iEntity Entity) => { return Entity.Name == Name; });

            if (Target.UniqueID == 0)
                return null;

            return Target;
        }

        public List<iEntity> Query(Rectangle _Area)
        {
            if (Bounds.Contains(_Area))
            {
                List<iEntity> Entities = new List<iEntity>();

                foreach (Sector Sect in GetSectors(_Area))
                {
                    Entities.AddRange(Sect.GetEntities(_Area));
                }

                return Entities;
            }
            else
            {
                if (_depth != 0)
                    return _parent.Query(_Area);
                else
                    return new List<iEntity>();
            }
        }

        private List<iEntity> GetEntities(Rectangle _Area)
        {
            List<iEntity> Entities = new List<iEntity>();

            for (int x = 0; x < Count; x++)
            {
                if (_Area.IntersectsWith(Objects[x].Area))
                    Entities.Add(Objects[x]);
            }

            return Entities;
        }

        public List<Sector> GetSectors(Rectangle Area)
        {
            if (!Bounds.IntersectsWith(Area))
                return new List<Sector>();
            
            List<Sector> Sectors = new List<Sector>();

            if (_childBL == null)
            {
                Sectors.Add(this);
                return Sectors;
            }

            if (_childBL.Bounds.IntersectsWith(Area))
                Sectors.AddRange(_childBL.GetSectors(Area));

            if (_childBR.Bounds.IntersectsWith(Area))
                Sectors.AddRange(_childBR.GetSectors(Area));

            if (_childTL.Bounds.IntersectsWith(Area))
                Sectors.AddRange(_childTL.GetSectors(Area));

            if (_childTR.Bounds.IntersectsWith(Area))
                Sectors.AddRange(_childTR.GetSectors(Area));

            return Sectors;

        }

        public int Count
        {
            get
            {
                if (Objects != null)
                    return Objects.Count;
                else return 0;
            }
        }

        public int GetChildCount
        {
            get
            {
                if (_childBL == null)
                    return Count;

                return _childBL.GetChildCount + _childBR.GetChildCount + _childTL.GetChildCount + _childTR.GetChildCount;
            }
        }

        private Sector GetDesinationTree(iEntity Entity)
        {
            if (_childTL == null)
                return this;

            if (_childTL.Bounds.Contains(Entity.Location))
            {
                return _childTL;
            }
            else if (_childTR.Bounds.Contains(Entity.Location))
            {
                return _childTR;
            }
            else if (_childBL.Bounds.Contains(Entity.Location))
            {
                return _childBL;
            }
            else if (_childBR.Bounds.Contains(Entity.Location))
            {
                return _childBR;
            }

            return this;
        }

        private void MoveExisting()
        {
            int MoveCount = 0;
            int OriginalCount = Count;

            while (Count > 0)
            {
                Move(Objects[Count - 1]);
                MoveCount++;

                if (MoveCount > OriginalCount)
                    break;
            }
        }

        private void RemoveDivisions()
        {
            _childBL = null;
            _childBR = null;
            _childTL = null;
            _childTR = null;
        }

        private void Divide()
        {
            Point size = new Point(Bounds.Width / 2, Bounds.Height / 2);
            Point mid = new Point(Bounds.X + size.X, Bounds.Y + size.Y);

            _childTL = new Sector(new Rectangle(Bounds.Left, Bounds.Top, size.X, size.Y), this, _mapParent);
            _childTR = new Sector(new Rectangle(mid.X, Bounds.Top, size.X, size.Y), this, _mapParent);
            _childBL = new Sector(new Rectangle(Bounds.Left, mid.Y, size.X, size.Y), this, _mapParent);
            _childBR = new Sector(new Rectangle(mid.X, mid.Y, size.X, size.Y), this, _mapParent);
        }
    }
}
